#include "D3D11.h"
#include "Util.h"


namespace TestDX
{

D3D11::D3D11(void) :
	_pSwapChain(NULL),
	_pDevice(NULL),
	_pDeviceContext(NULL),
	_pRenderTargetView(NULL),
	_pDepthStencilBuffer(NULL),
	_pDepthStencilView(NULL),
	_pRasterState(NULL),
	_projectionMatrix(),
	_orthoMatrix(),
	_worldMatrix()
{
}


D3D11::~D3D11(void)
{
	Shutdown();
}

bool D3D11::Init(
	HWND hwnd,
	int width,
	int height,
	float screenDepth,
	float screenNear)
{
	// Inicializo y creo swap chain ===================================
	DXGI_SWAP_CHAIN_DESC scd;
	ZeroMemory(&scd, sizeof(scd));

	// DXGI_MODE_DESC
	scd.BufferDesc.Width = width;
	scd.BufferDesc.Height = height;
	// @ TODO: vsync achieved by obtaining refresh rate from adapter
	scd.BufferDesc.RefreshRate.Numerator = 0;
	scd.BufferDesc.RefreshRate.Denominator = 1;
	scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	scd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	scd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

	// DXGI_SAMPLE_DESC
	// We use default sampler mode (no anti-aliasing)
	scd.SampleDesc.Count = 1; // multisamples per pixel
	scd.SampleDesc.Quality = 0; // The higher the quality, the lower the performance

	scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	scd.BufferCount = 1; // single back buffer, i.e. double-buffered
	scd.OutputWindow = hwnd;
	scd.Windowed = true;
	scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	scd.Flags = 0; // No advanced flags

	// This way we get the higher feature level possible (see D3D11CreateDeviceAndSwapChain help page)
	D3D_FEATURE_LEVEL featureLevel ;
	HR(D3D11CreateDeviceAndSwapChain(
			NULL,
			D3D_DRIVER_TYPE_HARDWARE,
			NULL,
			0,
			NULL, 0,
			D3D11_SDK_VERSION,
			&scd,
			&_pSwapChain,
			&_pDevice,
			&featureLevel,
			&_pDeviceContext));

	if(featureLevel != D3D_FEATURE_LEVEL_11_0)
	{
		// @ TODO: Log: No DX11 support!
		return false;
	}

	ID3D11Texture2D* pBackBuffer;
	HR(_pSwapChain->GetBuffer(0,
							  __uuidof(pBackBuffer),
							  (LPVOID*)&pBackBuffer));

	HR(_pDevice->CreateRenderTargetView(pBackBuffer,
										NULL,
										&_pRenderTargetView));

	pBackBuffer->Release();
	pBackBuffer = NULL;

	// Setup and create depth buffer =======================================
	D3D11_TEXTURE2D_DESC dbd;
	ZeroMemory(&dbd, sizeof(dbd));

	dbd.Width = width;
	dbd.Height = height;
	dbd.MipLevels = 1;
	dbd.ArraySize = 1;
	dbd.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	// Same SampleDesc as swap chain buffers!
	dbd.SampleDesc = scd.SampleDesc;
	dbd.Usage = D3D11_USAGE_DEFAULT;
	dbd.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	dbd.CPUAccessFlags = 0;
	dbd.MiscFlags = 0;

	HR(_pDevice->CreateTexture2D(&dbd,
								 NULL,
								 &_pDepthStencilBuffer));

	// Set default depth/stencil state: depth on, stencil off.
	_pDeviceContext->OMSetDepthStencilState(NULL, 1); // <-- Not sure if it's needed as long as we use default

	D3D11_DEPTH_STENCIL_VIEW_DESC dsvd;
	ZeroMemory(&dsvd, sizeof(dsvd));

	dsvd.Format = dbd.Format;
	dsvd.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
	dsvd.Texture2D.MipSlice = 0;
	dsvd.Flags = 0;

	HR(_pDevice->CreateDepthStencilView(_pDepthStencilBuffer,
									   &dsvd,
									   &_pDepthStencilView));

	_pDeviceContext->OMSetRenderTargets(1,
										&_pRenderTargetView,
										_pDepthStencilView);

	// Setup rasterizer      =======================================
	D3D11_RASTERIZER_DESC rd;
	ZeroMemory(&rd, sizeof(rd));

	rd.FillMode = D3D11_FILL_SOLID;
	rd.CullMode = D3D11_CULL_BACK;
	rd.FrontCounterClockwise = false;
	rd.DepthBias = 0;
	rd.DepthBiasClamp = 0.0f;
	rd.SlopeScaledDepthBias = 0.0f;
	rd.DepthClipEnable = true;
	rd.ScissorEnable = false;
	rd.MultisampleEnable = false;
	rd.AntialiasedLineEnable = false;

	HR(_pDevice->CreateRasterizerState(&rd, &_pRasterState));

	_pDeviceContext->RSSetState(_pRasterState);

	// Setup and create vp =======================================
	D3D11_VIEWPORT vp;
	ZeroMemory(&vp, sizeof(vp));

	vp.TopLeftX = 0.0f;
	vp.TopLeftY = 0.0f;
	vp.Width = (float)width;
	vp.Height = (float)height;
	vp.MinDepth = 0.0f;
	vp.MaxDepth = 1.0f;

	_pDeviceContext->RSSetViewports(1, &vp);

	// Matrix setup =================================================
	XMStoreFloat4x4(&_projectionMatrix,
					XMMatrixPerspectiveFovLH((float)XM_PI / 4.0f,
											 (float)width / (float)height,
											 screenNear,
											 screenDepth));

	XMStoreFloat4x4(&_orthoMatrix,
					XMMatrixOrthographicLH((float) width,
										   (float) height,
										   screenNear,
										   screenDepth));

	XMStoreFloat4x4(&_worldMatrix, XMMatrixIdentity());

	return true;
}

void D3D11::Shutdown()
{
	RELEASE_COM(_pRasterState);
	RELEASE_COM(_pDepthStencilView);
	RELEASE_COM(_pDepthStencilBuffer);
	RELEASE_COM(_pRenderTargetView);
	RELEASE_COM(_pDeviceContext);
	RELEASE_COM(_pDevice);
	RELEASE_COM(_pSwapChain);
}

void D3D11::BeginScene()
{
	float clearColor[] = {0.2f, 0.2f, 0.2f, 0.2f};

	_pDeviceContext->ClearRenderTargetView(_pRenderTargetView, clearColor);
	_pDeviceContext->ClearDepthStencilView(_pDepthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);
}

void D3D11::EndScene()
{
	// Present on vsync
	_pSwapChain->Present(1, 0);
}

}